package etcdmutex

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/zackeus/goutil/strutil"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/concurrency"
	"go.uber.org/atomic"
	"sync"
)

const (
	defaultPfx = "mutex/"
	defaultKey = "global/key"
	defaultTTl = 60
)

var (
	hasClosedErr = errors.New("the EtcdMutex client has closed")
)

type (
	// ManagerOption 可选项
	ManagerOption struct {
		f func(*managerOptions)
	}

	managerOptions struct {
		prefix string
	}

	// MutexOption 可选项
	MutexOption struct {
		f func(*mutexOptions)
	}

	mutexOptions struct {
		ttl int
		key string
	}

	// EtcdMutex Etcd 实现的分布式锁
	EtcdMutex struct {
		session *concurrency.Session
		mutex   *concurrency.Mutex
	}

	MutexManager struct {
		once   sync.Once
		closed atomic.Bool
		client *clientv3.Client
		// etcd key 前缀
		prefix string
	}
)

func New(client *clientv3.Client, options ...ManagerOption) *MutexManager {
	do := managerOptions{
		prefix: defaultPfx,
	}
	for _, option := range options {
		option.f(&do)
	}

	m := &MutexManager{
		client: client,
		prefix: do.prefix,
	}
	m.closed.Store(false)
	return m
}

// Available 判断是否可用
func (m *MutexManager) Available() bool {
	if m.client == nil || m.closed.Load() {
		return false
	}
	return true
}

// Acquire 加锁
// key: 加锁key
// ttl: session 超时时间(可防止应用宕机而死锁)
func (m *MutexManager) Acquire(ctx context.Context, options ...MutexOption) (*EtcdMutex, error) {
	if m.closed.Load() {
		/* 分布式锁已关闭 */
		return nil, hasClosedErr
	}

	do := mutexOptions{
		key: defaultKey,
		ttl: defaultTTl,
	}
	for _, option := range options {
		option.f(&do)
	}

	/* 构建 session */
	session, err := concurrency.NewSession(m.client, concurrency.WithTTL(do.ttl))
	if err != nil {
		return nil, err
	}

	mu := &EtcdMutex{
		session: session,
		mutex:   concurrency.NewMutex(session, m.prefix+do.key),
	}
	/* 加锁 */
	err = mu.mutex.Lock(ctx)
	return mu, err
}

// Release 锁释放
func (l *EtcdMutex) Release(ctx context.Context) error {
	defer func(session *concurrency.Session) {
		_ = session.Close()
	}(l.session)

	return l.mutex.Unlock(ctx)
}

// Shutdown 关闭分布式锁
// g: 是否关闭client
func (m *MutexManager) Shutdown(g ...bool) {
	m.once.Do(func() {
		m.closed.Store(true)
		if m.client != nil && g != nil && g[0] {
			_ = m.client.Close()
		}
		m.client = nil
	})
}

func WithPrefix(prefix string) ManagerOption {
	return ManagerOption{func(do *managerOptions) {
		do.prefix = prefix
	}}
}

func WithTTL(ttl int) MutexOption {
	return MutexOption{func(do *mutexOptions) {
		do.ttl = ttl
	}}
}

func WithKey(key string, prefixes ...string) MutexOption {
	if prefixes != nil && len(prefixes) > 0 {
		prefix := ""
		for _, p := range prefixes {
			if strutil.IsNotBlank(p) {
				prefix = fmt.Sprintf("%s/%s", prefix, p)
			}
		}
		key = fmt.Sprintf("%s/%s", prefix, key)
	}

	return MutexOption{func(do *mutexOptions) {
		do.key = key
	}}
}
